package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	"math/rand"
	"sale_system/order_srv/global"
	"sale_system/order_srv/model"
	"sale_system/order_srv/proto"
	"time"
)

type OrderServer struct {
	proto.UnimplementedOrderServer // 一定需要实现这个接口
}

func (s *OrderServer) mustEmbedUnimplementedGoodsServer() {
	//TODO implement me
	panic("implement me")
}

// Paginate 分页函数
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {

		if page <= 0 {
			page = 1
		}
		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}
		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

func GenerateOrderSn(userId int32) string {

	// 年月日时分秒-用户ID-两位随机数
	now := time.Now()
	rand.Seed(time.Now().UnixNano())
	orderSn := fmt.Sprintf("%d%d%d%d%d%d%d%d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), userId, rand.Intn(89)+10)
	return orderSn
}

// CreateOrder 创建订单
func (s *OrderServer) CreateOrder(ctx context.Context, request *proto.OrderRequest) (*proto.OrderInfoResponse, error) {

	// 用来监听本地事务操作，并接收本地事务的数据
	orderListener := OrderListener{Ctx: ctx}

	address := fmt.Sprintf("%s:%d", global.ServerConfig.RocketMqInfo.Host, global.ServerConfig.RocketMqInfo.Port)
	Producer, err := rocketmq.NewTransactionProducer(&orderListener, producer.WithNameServer([]string{address}))
	if err != nil {
		zap.S().Info("获取生产者对象失败, ", err.Error())
		return nil, err
	}

	if err = Producer.Start(); err != nil {
		zap.S().Info("启动生产者对象失败，%s ", err.Error())
		return nil, err
	}

	order := model.OrderInfo{
		User:         request.UserId,
		OrderSn:      GenerateOrderSn(request.UserId),
		Address:      request.Address,
		SignerName:   request.Name,
		SingerMobile: request.Mobile,
		Post:         request.Post,
	}

	orderStr, err := json.Marshal(order)
	if err != nil {
		zap.S().Info("序列化订单下失败%s", err.Error())
		return nil, err
	}
	// 将订单的信息维护起来，发给库存子服务
	msg := primitive.NewMessage("InventoryReBack", orderStr)

	// 这个过程中会执行本地事务
	_, err = Producer.SendMessageInTransaction(context.Background(), msg)
	if err != nil {
		zap.S().Info("发送事务消息失败，%s", err.Error())
		return nil, status.Error(codes.Internal, "发送事务消息失败")
	}

	// 因为我们现在的业务逻辑是：事务进行归还库存
	if orderListener.Code != codes.OK { // 因为如果不是OK，说明事务处理过程出现失败
		zap.S().Info("新建订单失败")
		return nil, status.Error(orderListener.Code, "新建订单失败")
	}

	return &proto.OrderInfoResponse{
		Id:      orderListener.ID,
		OrderSn: order.OrderSn,
		Total:   orderListener.OrderAmount,
	}, nil
}

// DeleteOrder 删除订单
func (s *OrderServer) DeleteOrder(ctx context.Context, request *proto.OrderRequest) (*emptypb.Empty, error) {

	// 逻辑：从请求中获取到OrderSn
	// 1.订单未支付进行删除，首先先删除这条订单记录
	// 2.进行库存归还，向rocketmq发送库存归还信息
	//并且需要分布式事务来保证一致性

	return &emptypb.Empty{}, nil
}

func (s *OrderServer) OrderList(ctx context.Context, request *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {

	// 这个接口可能是用户来查询历史订单列表，也可以是后台管理员来查询历史的所有订单列表
	var orderInfos []model.OrderInfo
	var total int64
	global.DB.Model(&model.OrderInfo{User: request.UserId}).Count(&total) // 注意where语句是拼接查询语句，并没有执行
	zap.S().Info(total)
	var rsp proto.OrderListResponse
	rsp.Total = int32(total)

	// 注意不能用where语句只能用model！！！
	global.DB.Scopes(Paginate(int(request.Pages), int(request.PagePerNums))).Model(&model.OrderInfo{User: request.UserId}).Find(&orderInfos)

	for _, orderInfo := range orderInfos {
		orderRsp := proto.OrderInfoResponse{
			Id:      orderInfo.ID,
			UserId:  orderInfo.User,
			OrderSn: orderInfo.OrderSn,
			PayType: orderInfo.PayType,
			Status:  orderInfo.Status,
			Post:    orderInfo.Post,
			Address: orderInfo.Address,
			Name:    orderInfo.SignerName,
			Total:   orderInfo.OrderMount,
			Mobile:  orderInfo.SingerMobile,
			AddTime: orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
		}
		rsp.Data = append(rsp.Data, &orderRsp)
	}

	return &rsp, nil
}
func (s *OrderServer) OrderDetail(ctx context.Context, request *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {

	var orderInfo model.OrderInfo
	// 先根据ID从数据库所有订单中找到目标的订单，获取订单的详细数据
	result := global.DB.Where(&model.OrderInfo{BaseModel: model.BaseModel{ID: request.Id}, User: request.UserId}).First(&orderInfo)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "订单不存在")
	}
	var rsp proto.OrderInfoDetailResponse
	_orderInfo := proto.OrderInfoResponse{}

	_orderInfo.Id = orderInfo.ID
	_orderInfo.UserId = orderInfo.User
	_orderInfo.OrderSn = orderInfo.OrderSn
	_orderInfo.PayType = orderInfo.PayType
	_orderInfo.Status = orderInfo.Status
	_orderInfo.Post = orderInfo.Post
	_orderInfo.Address = orderInfo.Address
	_orderInfo.Name = orderInfo.SignerName
	_orderInfo.Total = orderInfo.OrderMount
	_orderInfo.Mobile = orderInfo.SingerMobile

	rsp.OrderInfo = &_orderInfo

	var orderGoods []model.OrderGoods
	// 找到某个订单对应的所有商品
	result = global.DB.Where(&model.OrderGoods{Order: orderInfo.ID}).Find(&orderGoods)
	if result.Error != nil {
		return nil, result.Error
	}
	for _, orderGood := range orderGoods {

		goodInfo := proto.OrderItemResponse{
			GoodsId:    orderGood.Goods,
			GoodsName:  orderGood.GoodsName,
			GoodsPrice: orderGood.GoodsPrice,
			Nums:       orderGood.Nums,
			//GoodsImage: orderGood.GoodsImage,	// 这个不需要吗
		}
		rsp.Goods = append(rsp.Goods, &goodInfo)
	}
	return &rsp, nil
}
func (s *OrderServer) UpdateOrderStatus(ctx context.Context, request *proto.OrderStatus) (*emptypb.Empty, error) {

	// // 接口设计有点不合理，这里我们只能改一个状态信息，但是我们查表发现，并没有payType和trade_no字段
	// result := global.DB.Model(&model.OrderInfo{}).Where("order_sn", request.OrderSn).Save(&model.OrderInfo{})

	result := global.DB.Model(&model.OrderInfo{}).Where("order_sn", request.OrderSn).Update("status", request.Status)

	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "订单不存在")
	}
	return &emptypb.Empty{}, nil
}
